package procsim;

/**
 * Klasa koja emulira memoriju
 */
public class Memory implements Element{
    /**
     * Broj bitova jedne reci u memoriji.
     */
    private int numOfBits;
    /**
     * Broj reci u memoriji.
     */
    private int rows;
    /**
     * Struktura koja emulira memoriju.
     */
    private int[] data;
    /**
     * Ulazni kontrolni signal (read) koji oznacava da li treba da se cita iz memorije ili ne
     */
    private Signal rdS;
    /**
     * Ulazni kontrolni signal (write) koji oznacava da li treba da se pise u memoriju ili ne
     */
    private Signal wrS;
    /**
     * Ulazni podatak koji se upisuje u memoriju kada je wrS aktivan
     */
    private MSignal dIn;
    /**
     * Izlazni podatak koji se cita iz memorije kada je rdS aktivan
     */
    private MSignal dOut;
    /**
     * Adresa sa koje se cita ili u koju se upisuje podatak
     */
    private MSignal addr;
    /**
     * Pravi objekat memorije.
     * @param now broj reci u memoriji
     * @param nob broj bitova u jednoj reci
     */
    public Memory(int now, int nob){
        data=new int[rows=now];
        numOfBits=nob;
    }
    /**
     * Pravi objekat memorije sa podrazumevanim brojem bitova u reci (32).
     * @param now broj reci u memoriji
     */
    public Memory(int now){
        this(now, 32);
    }
    /**
     * Postavlja read signal memorije na zadati signal
     * @param rd signal koji se postavlja za ulazni read signal
     * @return objekat memorije
     */
    public Memory setRdSignal(Signal rd){
        if(rd!=null) rd.addElement(this);
        rdS=rd;
        if(wrS!=null) wrS.set(0);
        return this;
    }
    /**
     * Postavlja write signal memorije na zadati signal
     * @param wr signal koji se postavlja za ulazni write signal
     * @return objekat memorije
     */
    public Memory setWrSignal(Signal wr){
        if(wr!=null) wr.addElement(this);
        wrS=wr;
        if(rdS!=null) rdS.set(0);
        return this;
    }
    /**
     * Postavlja input signal memorije na zadati signal
     * @param in signal koji se postavlja za ulazni "data in" signal
     * @return objekat memorije
     */
    public Memory setInput(MSignal in){
        if(in!=null) in.addElement(this);
        dIn=in;
        return this;
    }
    /**
     * Postavlja address signal memorije na zadati signal
     * @param addr signal koji se postavlja za ulazni "address" signal
     * @return objekat memorije
     */
    public Memory setAddress(MSignal addr){
        if(addr!=null) addr.addElement(this);
        this.addr = addr;
        return this;
    }
    /**
     * Postavlja output signal memorije na zadati signal
     * @param sig signal koji se postavlja za izlazni "data out" signal
     * @return objekat memorije
     */
    public Memory setOutput(MSignal sig){
        dOut=sig;
        return this;
    }
    /**
     * Upisuje u memoriju na zadatu adresu zadati podatak (kao ceo broj).
     * @param address zadata adresa
     * @param dat zadati podatak
     * @return objekat memorije
     */
    public Memory write(int address, int dat){
        if (address>=rows || address<0){
            System.err.println("Cannot write to address "+address);
            return this;
        }
        data[address]=dat;
        return this;
    }
    /**
     * Upisuje u memoriju na zadatu adresu zadati podatak (kao objekat MSignal).
     * @param address zadata adresa
     * @param dat zadati podatak
     * @return objekat memorije
     */
    public Memory write(int address, MSignal dat){
        return write(address, dat.get());
    }
    /**
     * Cita iz memorije podatak sa zadate adrese.
     * @param address
     * @return
     */
    public MSignal read(int address){
        MSignal ret = readLightweight(address);
        if(ret==null) return new MSignal(numOfBits, 0);
        else return ret;
    }
    /**
     * Cita iz memorije podatak sa zadate adrese.
     * Ne stvara nepotrebne objekte, vec vraca null ako je vrednost 0 u memoriji.
     * @param address adresa u memoriji
     * @return null ako je sadrzaj odgovarajuce memorijske lokacije 0, inace objekat klase MSignal
     */
    public MSignal readLightweight(int address){
        if (address>=rows || address<0){
            System.err.println("Cannot read from address "+address);
            return null;
        }
        int ret = data[address];
        if(ret==0) return null;
        else return new MSignal(numOfBits, ret);
    }
    /**
     * Vraca broj bitova u jednoj reci.
     * @return broj bitova
     */
    public int numberOfBits(){
        return numOfBits;
    }
    /**
     * Vraca broj reci u memoriji.
     * @return broj reci
     */
    public int numberOfWords(){
        return rows;
    }
    /**
     * Emulira jedan ciklus signala takta. Sinhorno se vrsi citanje ili upis, u zavisnosti od postavljenih ulaznih signala.
     * @return objekat memorije
     */
    @Override
    public Memory tick() {
        if(addr!=null && dOut!=null && rdS!=null && !rdS.isNull())
            dOut.set(read(addr.get()));
        else{
            dOut.set(0);
            if(addr!=null && dIn!=null && wrS!=null && !wrS.isNull())
                write(addr.get(), dIn);   
        }
        return this;
    }
    /**
     * Vraca dOut sa DataOut izlaza = procitani podatak
     * @return objekat memorije
     */
    @Override
    public MSignal result(){
        return dOut;
    }
    /**
     * Azurira sve ulazne signale. Metodu pozivaju signali koji su povezani na ovu komponentu.
     * @param sig ulazni signal
     */
    @Override
    public void updateSignal(Sig sig){
        if(rdS!=null && rdS==sig)
            rdS.set(sig.get(), false);
        if(wrS!=null && wrS==sig)
            wrS.set(sig.get(), false);
        if(dIn!=null && dIn==sig)
            dIn.set(sig.get(), false);
        if(addr!=null && addr==sig)
            addr.set(sig.get(), false);
    }
}
